<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
  <title>Apache Tomcat WebSocket Examples: Multiplayer Snake</title>
  <style type="text/css">
    #playground {
      width: 640px;
      height: 480px;
      background-color: #000000;
    }

    #console-container {
      float: left;
      margin-left: 15px;
      width: 300px;
    }

    #console {
      border: 1px solid #CCCCCC;
      border-right-color: #999999;
      border-bottom-color: #999999;
      height: 480px;
      overflow-y: scroll;
      padding-left: 5px;
      padding-right: 5px;
      width: 100%;
    }

    #console p {
      padding: 0;
      margin: 0;
    }
  </style>
  <script src="https://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script>
</head>
<body>
<noscript><h2 style="color: #FF0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being
                                     enabled. Please enable
                                     Javascript and reload this page!</h2></noscript>

<div style="float: left">
  <canvas height="480" id="playground" width="640"></canvas>
</div>
<div id="console-container">
  <div id="console"></div>
</div>
<script type="text/javascript">

  var Game = {};

  Game.fps = 30;
  Game.socket = null;
  Game.nextFrame = null;
  Game.interval = null;
  Game.direction = 'none';
  Game.gridSize = 10;

  function Snake() {
    this.snakeBody = [];
    this.color = null
  }

  Snake.prototype.draw = function (context) {
    for (var id in this.snakeBody) {
      context.fillStyle = this.color;
      context.fillRect(this.snakeBody[id].x, this.snakeBody[id].y, Game.gridSize, Game.gridSize)
    }
  };

  Game.initialize = function () {
    this.entities = [];
    canvas = document.getElementById('playground');
    if (!canvas.getContext) {
      Console.log('Error: 2d canvas not supported by this browser.');
      return
    }
    this.context = canvas.getContext('2d');
    window.addEventListener('keydown', function (e) {
      var code = e.keyCode;
      if (code > 36 && code < 41) {
        switch (code) {
          case 37:
            if (Game.direction != 'east') Game.setDirection('west');
            break;
          case 38:
            if (Game.direction != 'south') Game.setDirection('north');
            break;
          case 39:
            if (Game.direction != 'west') Game.setDirection('east');
            break;
          case 40:
            if (Game.direction != 'north') Game.setDirection('south');
            break
        }
      }
    }, false);
    Game.connect()
  };

  Game.setDirection = function (direction) {
    Game.direction = direction;
    Game.socket.send(direction);
    Console.log('Sent: Direction ' + direction)
  };

  Game.startGameLoop = function () {
    if (window.webkitRequestAnimationFrame) {
      Game.nextFrame = function () {
        webkitRequestAnimationFrame(Game.run)
      }
    } else if (window.mozRequestAnimationFrame) {
      Game.nextFrame = function () {
        mozRequestAnimationFrame(Game.run)
      }
    } else {
      Game.interval = setInterval(Game.run, 1000 / Game.fps)
    }
    if (Game.nextFrame != null) {
      Game.nextFrame()
    }
  };

  Game.stopGameLoop = function () {
    Game.nextFrame = null;
    if (Game.interval != null) {
      clearInterval(Game.interval)
    }
  };

  Game.draw = function () {
    this.context.clearRect(0, 0, 640, 480);
    for (var id in this.entities) {
      this.entities[id].draw(this.context)
    }
  };

  Game.addSnake = function (id, color) {
    Game.entities[id] = new Snake();
    Game.entities[id].color = color
  };

  Game.updateSnake = function (id, snakeBody) {
    if (typeof Game.entities[id] != 'undefined') {
      Game.entities[id].snakeBody = snakeBody
    }
  };

  Game.removeSnake = function (id) {
    Game.entities[id] = null;
    // Force GC.
    delete Game.entities[id]
  };

  Game.run = (function () {
    var skipTicks = 1000 / Game.fps, nextGameTick = (new Date).getTime();

    return function () {
      while ((new Date).getTime() > nextGameTick) {
        nextGameTick += skipTicks
      }
      Game.draw();
      if (Game.nextFrame != null) {
        Game.nextFrame()
      }
    }
  })();

  Game.connect = (function () {
    Game.socket = new SockJS('/snake');

    Game.socket.onopen = function () {
      // Socket open.. start the game loop.
      Console.log('Info: WebSocket connection opened.');
      Console.log('Info: Press an arrow key to begin.');
      Game.startGameLoop();
      setInterval(function () {
        // Prevent server read timeout.
        Game.socket.send('ping')
      }, 5000)
    };

    Game.socket.onclose = function () {
      Console.log('Info: WebSocket closed.');
      Game.stopGameLoop()
    };

    Game.socket.onmessage = function (message) {
      // _Potential_ security hole, consider using json lib to parse data in production.
      var packet = eval('(' + message.data + ')');
      switch (packet.type) {
        case 'update':
          for (var i = 0; i < packet.data.length; i++) {
            Game.updateSnake(packet.data[i].id, packet.data[i].body)
          }
          break;
        case 'join':
          for (var j = 0; j < packet.data.length; j++) {
            Game.addSnake(packet.data[j].id, packet.data[j].color)
          }
          break;
        case 'leave':
          Game.removeSnake(packet.id);
          break;
        case 'dead':
          Console.log('Info: Your snake is dead, bad luck!');
          Game.direction = 'none';
          break;
        case 'kill':
          Console.log('Info: Head shot!');
          break
      }
    }
  });

  var Console = {};

  Console.log = (function (message) {
    var console = document.getElementById('console');
    var p = document.createElement('p');
    p.style.wordWrap = 'break-word';
    p.innerHTML = message;
    console.appendChild(p);
    while (console.childNodes.length > 25) {
      console.removeChild(console.firstChild)
    }
    console.scrollTop = console.scrollHeight
  });

  Game.initialize()
</script>
</body>
</html>
